
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import org.encog.Encog;
import org.encog.engine.network.activation.ActivationSigmoid;
import org.encog.ml.data.MLData;
import org.encog.ml.data.MLDataPair;
import org.encog.ml.data.MLDataSet;
import org.encog.ml.data.basic.BasicMLData;
import org.encog.ml.data.basic.BasicMLDataSet;
import org.encog.neural.networks.BasicNetwork;
import org.encog.neural.networks.layers.BasicLayer;
import org.encog.neural.networks.training.propagation.resilient.ResilientPropagation;
import org.encog.persist.EncogDirectoryPersistence;
import org.encog.util.arrayutil.NormalizationAction;
import org.encog.util.arrayutil.NormalizedField;

public class MyNeuralNetwork {

    //Normalizadores
    NormalizedField normx0;
    NormalizedField normx1;
    NormalizedField normx2;
    NormalizedField normx3;
    //Variáveis de entrada e saída
    double in[][] = new double[1132][3];
    double out[][] = new double[1132][1];
    //Nome do arquivo com a rede
    private final String networkName = "trainnedNetwork";
    //Arquivo com a rede treinada
    private File filePath = new File(new File("").getAbsolutePath() + File.separator + "logs" + File.separator + networkName);
    //Imprimir dados de treinamento
    private boolean printTrainingSet = false;
    //
    private BasicNetwork network;

    public MyNeuralNetwork() {
    }

    public double estimateWater(int input1, int input2, int input3, int input4) {
        double set[] = new double[4];
        set[0] = input1;
        set[1] = input2;
        set[2] = input3;
        set[3] = input4;
        MLData input = new BasicMLData(set);
        NormalizedField norm = new NormalizedField(NormalizationAction.Normalize,
                null, 10500, 500, 0, 1);
        return norm.deNormalize(network.compute(input).getData(0));
//        return network.compute(input).getData(0);
    }

    //Método utilizado para treinar a rede e gerar o arquivo com os pesos
    public void runTrainig() throws IOException, ClassNotFoundException {

        //Ler arquivos do rick
        loadTrainingSet(printTrainingSet);
        normalize_all();

        //Create a neural network, without using a factory
        this.network = new BasicNetwork();
        network.addLayer(new BasicLayer(new ActivationSigmoid(), true, 3));
        network.addLayer(new BasicLayer(new ActivationSigmoid(), true, 6));
        //22 Epoch #1184795 Error:0.008406904076258307
        //30 #1999999 Error:0.015119738905110964
        network.addLayer(new BasicLayer(new ActivationSigmoid(), true, 1));
        network.getStructure().finalizeStructure();
        network.reset();

        //Create training data
        MLDataSet trainingSet = new BasicMLDataSet(in, out);

        //Train the neural network
        final ResilientPropagation train = new ResilientPropagation(network, trainingSet);

        int epoch = 1;

        do {
            train.iteration();
            System.out.println("Epoch #" + epoch + " Error:" + train.getError());
            epoch++;
        } while (train.getError() > 0.001 && epoch < 50000);
        train.finishTraining();

        //Test the neural network
        System.out.println("Neural Network Results:");
        for (MLDataPair pair : trainingSet) {
            final MLData output = network.compute(pair.getInput());
            System.out.println(pair.getInput().getData(0) + " , " + pair.getInput().getData(1) + " , "
                    + pair.getInput().getData(2) + " , " + pair.getInput().getData(3)
                    + "  actual=" + output.getData(0) + ",ideal=" + pair.getIdeal().getData(0));
        }
        //Salva o arquivo de treinamento;
        save(filePath, network);
        Encog.getInstance().shutdown();
    }

    public void loadTrainingSet(boolean printData) {
        File dir = new File(new File("").getAbsolutePath() + File.separator + "logs");
        File arq = new File(dir, "arquivo_treinamento");
        try {

            FileReader fileReader = new FileReader(arq);
            //Criamos o objeto bufferReader que nos
            //Oferece o método de leitura readLine()
            BufferedReader bufferedReader = new BufferedReader(fileReader);

            //String que irá receber cada linha do arquivo
            String linha = "";
            int i = 0;
            while ((linha = bufferedReader.readLine()) != null) {
                //Aqui imprimimos a linha
                String[] linha2 = linha.split("\t");
                double in2[] = new double[4];
                double out2[] = new double[1];
                in2[0] = Double.valueOf(linha2[0]);
                in2[1] = Double.valueOf(linha2[1]);
                in2[2] = Double.valueOf(linha2[2]);
                out2[0] = Double.valueOf(linha2[3]);
                in[i] = in2;
                out[i] = out2;
                i++;

                if (printData) {
                    System.out.println(linha2[0] + "\t" + linha2[1] + "\t" + linha2[2] + "\t" + linha2[3] + "\t" + linha2[4]);
                }
            }
            //liberamos o fluxo dos objetos ou fechamos o arquivo
            fileReader.close();
            bufferedReader.close();
        } catch (IOException e) {
        }
    }

    public void load(File file) {
        if (file == null) {
            this.network = (BasicNetwork) EncogDirectoryPersistence.loadObject(filePath);
        } else {
            this.network = (BasicNetwork) EncogDirectoryPersistence.loadObject(file);
        }
    }

    private void save(File file, BasicNetwork network) throws IOException, ClassNotFoundException {
        EncogDirectoryPersistence.saveObject(file, network);
    }

    //Aqui pode estar o erro
    private void normalize_all() {
        NormalizedField normx0 = new NormalizedField(NormalizationAction.Normalize,
                null, getMax(in, 0), getMin(in, 0), 0, 1);
        NormalizedField normx1 = new NormalizedField(NormalizationAction.Normalize,
                null, getMax(in, 1), getMin(in, 1), 0, 1);
        NormalizedField normx2 = new NormalizedField(NormalizationAction.Normalize,
                null, getMax(in, 2), getMin(in, 2), 0, 1);
        NormalizedField normx3 = new NormalizedField(NormalizationAction.Normalize,
                null, getMax(out, 0), getMin(out, 0), 0, 1);

        for (int i = 0; i < 1132; i++) {
            in[i][0] = normx0.normalize(in[i][0]);
            in[i][1] = normx1.normalize(in[i][1]);
            in[i][2] = normx2.normalize(in[i][2]);
            out[i][0] = normx2.normalize(out[i][0]);
            System.out.println(in[i][0] + "\t" + in[i][1] + "\t" + in[i][2] + "\t" + out[i][0] + "\t");
        }
    }

    private double getMin(double input[][], int i) {
        double menor = input[0][i];
        for (int j = 0; j < 1131; j++) {
            if (menor > input[j][i]) {
                menor = input[j][i];
            }
        }
        return menor;
    }

    private double getMax(double input[][], int i) {
        double maior = input[0][i];
        for (int j = 0; j < 1131; j++) {
            if (maior < input[j][i]) {
                maior = input[j][i];
            }
        }
        return maior;
    }

    public File getFilePath() {
        return filePath;
    }

    public void setFilePath(File filePath) {
        this.filePath = filePath;
    }
}